package parser;

import java.util.ArrayList;

import scanner.LexicalException;
import scanner.Scanner;
import scanner.Token;
import util.AST.AST;
import util.AST.Program;
import util.AST.Command.Command;
import util.AST.Command.FunctionBody;
import util.AST.Command.FunctionDeclaration;
import util.AST.Command.VariableDeclaration;
import util.AST.Expression.BinaryExpression;
import util.AST.Expression.BooleanUnaryExpression;
import util.AST.Expression.Expression;
import util.AST.Expression.IdentifierUnaryExpression;
import util.AST.Expression.NumberUnaryExpression;
import util.AST.RHS.CallStatementRHS;
import util.AST.RHS.ExpressionRHS;
import util.AST.RHS.RHS;
import util.AST.Statement.AssignStatement;
import util.AST.Statement.BreakStatement;
import util.AST.Statement.CallStatement;
import util.AST.Statement.ContinueStatement;
import util.AST.Statement.IfElseStatement;
import util.AST.Statement.PrintlnStatement;
import util.AST.Statement.ReturnStatement;
import util.AST.Statement.Statement;
import util.AST.Statement.WhileStatement;
import util.AST.Terminal.BooleanValue;
import util.AST.Terminal.Identifier;
import util.AST.Terminal.NumberValue;
import util.AST.Terminal.Operator;
import util.AST.Terminal.Terminal;
import util.AST.Terminal.Type;

/**
 * Parser class
 * @version 2010-august-29
 * @discipline Projeto de Compiladores
 * @author Gustavo H P Carvalho
 * @email gustavohpcarvalho@ecomp.poli.br
 */
public class Parser {

	// The current token
	private Token currentToken = null;
	// The scanner
	private Scanner scanner = null;
	
	private ArrayList<Command> cmds;
	
	/**
	 * Parser constructor
	 */
	public Parser() {
		// Initializes the scanner object
		this.scanner = new Scanner();
        try {
            this.currentToken = this.scanner.getNextToken();
	    } catch (LexicalException e) {                  
	            e.printStackTrace();
	    }
	}
	
	/**
	 * Veririfes if the current token kind is the expected one
	 * @param kind
	 * @throws SyntacticException
	 */ //TODO
	private void accept(int kind) throws SyntacticException, LexicalException {
		// If the current token kind is equal to the expected
			// Gets next token
		// If not
			// Raises an exception
		
		if(this.currentToken.getKind() == kind){
			this.currentToken = scanner.getNextToken();
			
		}
		else{
			throw new SyntacticException("Syntatic Exception", this.currentToken);
		}
	}
	
	/**
	 * Gets next token
	 */ //TODO
	private void acceptIt() throws LexicalException{
		this.currentToken = scanner.getNextToken();
	}

	/**
	 * Verifies if the source program is syntactically correct
	 * @throws SyntacticException
	 */ //TODO
	public AST parse() throws SyntacticException,LexicalException {
		Program program = parseProgram();
		return program;
	}
	
	//"identifier" | "number" | "false" | "true"
	private Terminal parseValue() throws SyntacticException, LexicalException{
		
		Terminal terminal;
		
		switch(this.currentToken.getKind())	{
			case GrammarSymbols.ID:
				terminal = new Identifier(this.currentToken.getSpelling());
				acceptIt();
				break;
			case GrammarSymbols.NUMBER:
				terminal = new NumberValue(this.currentToken.getSpelling());
				acceptIt();
				break;
			case GrammarSymbols.FALSE:
				terminal = new BooleanValue(this.currentToken.getSpelling());
				acceptIt();
				break;
			default:
				terminal = new BooleanValue(this.currentToken.getSpelling());
				accept(GrammarSymbols.TRUE);
		}
		
		return terminal;
	}
	
	//"(" Expression ( + | - | * | / | == | != | > | >= | < | <= ) Expression ")" | Value
	private Expression parseExpression() throws SyntacticException, LexicalException{
		
		Expression expression;
		
		if(this.currentToken.getKind() == GrammarSymbols.LPAR){
			acceptIt();
			Expression left = parseExpression();
			Operator operator = new Operator(this.currentToken.getSpelling());
			switch(this.currentToken.getKind()){
				case GrammarSymbols.PLUS:
				case GrammarSymbols.MINUS:
				case GrammarSymbols.MULT:
				case GrammarSymbols.DIV:
				case GrammarSymbols.EQUAL:
				case GrammarSymbols.NOTEQUAL:
				case GrammarSymbols.GREATERTHAN:
				case GrammarSymbols.GREATEREQUALTHAN:
				case GrammarSymbols.LESSERTHAN:
					acceptIt();
					break;
				default:
					accept(GrammarSymbols.LESSEREQUALTHAN);
			}
			Expression right = parseExpression();
			accept(GrammarSymbols.RPAR);
			expression = new BinaryExpression(left, operator, right);
		}
		else{
			Terminal terminal = parseValue();
			if(terminal instanceof Identifier){
				expression = new IdentifierUnaryExpression((Identifier) terminal);
			}
			else if(terminal instanceof NumberValue){
				expression = new NumberUnaryExpression((NumberValue) terminal);
			}
			else{
				expression = new BooleanUnaryExpression((BooleanValue) terminal);
			}
		}
		
		return expression;
	}
	
	//"(" Expression ")" | "identifier" "(" (Arguments | empty) ")"
	private RHS parseRHS() throws SyntacticException, LexicalException{
		
		RHS rhs;
		ArrayList<Identifier> ids = new ArrayList<Identifier>();
		
		if(this.currentToken.getKind() == GrammarSymbols.LPAR){
			acceptIt();
			Expression expression = parseExpression();
			rhs = new ExpressionRHS(expression);
			accept(GrammarSymbols.RPAR);
		}
		else{
			Identifier identifier = new Identifier(this.currentToken.getSpelling());
			accept(GrammarSymbols.ID);
			accept(GrammarSymbols.LPAR);
			if(this.currentToken.getKind() == GrammarSymbols.ID){
				ids = parseArguments();
			}
			accept(GrammarSymbols.RPAR);
			CallStatement cs = new CallStatement(identifier, ids);
			rhs = new CallStatementRHS(cs);
		}
		
		return rhs;
	}
	
	//"identifier" (empty | "," Arguments)
	private ArrayList<Identifier> parseArguments() throws SyntacticException, LexicalException{
		ArrayList<Identifier> ids = new ArrayList<Identifier>();
		Identifier identifier = new Identifier(this.currentToken.getSpelling());
		ids.add(identifier);
		accept(GrammarSymbols.ID);
		while(this.currentToken.getKind() == GrammarSymbols.COMMA){
			acceptIt();
			identifier = new Identifier(this.currentToken.getSpelling());
			ids.add(identifier);
			accept(GrammarSymbols.ID);			
		}
		
		return ids;
	}
	
	//"identifier" ( "(" (Arguments | empty) ")" | "=" RHS) ";" | 
	//"if" "(" Expression ")" "{" Statements "}" (empty | "else" "{" Statements "}") | 
	//"while" "(" Expression ")" "{" Statements "}" | "return" (empty | Expression) ";" | "break" ";" | 
	//"continue" ";" | "println" "(" "identifier" ")" ";"
	private Statement parseStatement() throws SyntacticException, LexicalException{
		Statement statement;
		ArrayList<Identifier> ids = new ArrayList<Identifier>();
		if(this.currentToken.getKind() == GrammarSymbols.ID){
			Identifier identifier = new Identifier(this.currentToken.getSpelling());
			acceptIt();
			if(this.currentToken.getKind() == GrammarSymbols.LPAR){
				acceptIt();
				if(this.currentToken.getKind() == GrammarSymbols.ID){
					ids = parseArguments();
				}
				accept(GrammarSymbols.RPAR);
				statement = new CallStatement(identifier, ids);
			}
			else{
				accept(GrammarSymbols.ASSIGN);
				RHS rhs = parseRHS();
				statement = new AssignStatement(identifier, rhs);
			}
			accept(GrammarSymbols.SEMICOLON);
		}
		else if(this.currentToken.getKind() == GrammarSymbols.IF){
			acceptIt();
			accept(GrammarSymbols.LPAR);
			Expression expression = parseExpression();
			accept(GrammarSymbols.RPAR);
			accept(GrammarSymbols.LBRACKET);
			ArrayList<Statement> sts = new ArrayList<Statement>();
			sts = parseStatements();
			ArrayList<Statement> ifSts = sts;
			accept(GrammarSymbols.RBRACKET);
			ArrayList<Statement> elseSts = new ArrayList<Statement>();
			if(this.currentToken.getKind() == GrammarSymbols.ELSE){
				acceptIt();
				accept(GrammarSymbols.LBRACKET);
				elseSts = parseStatements();
				accept(GrammarSymbols.RBRACKET);
			}
			statement = new IfElseStatement(expression, ifSts, elseSts);
		}
		else if(this.currentToken.getKind() == GrammarSymbols.WHILE){
			acceptIt();
			accept(GrammarSymbols.LPAR);
			Expression expression = parseExpression();
			accept(GrammarSymbols.RPAR);
			accept(GrammarSymbols.LBRACKET);
			ArrayList<Statement> sts = new ArrayList<Statement>();
			sts = parseStatements();
			accept(GrammarSymbols.RBRACKET);
			statement = new WhileStatement(expression, sts);
		}
		else if(this.currentToken.getKind() == GrammarSymbols.RETURN){
			acceptIt();
			Expression expression = null;
			if(this.currentToken.getKind() == GrammarSymbols.LPAR || 
			this.isValue(this.currentToken.getKind())){
				expression = parseExpression();
			}
			accept(GrammarSymbols.SEMICOLON);
			statement = new ReturnStatement(expression);
		}
		else if(this.currentToken.getKind() == GrammarSymbols.BREAK){
			acceptIt();
			accept(GrammarSymbols.SEMICOLON);
			statement = new BreakStatement();
		}
		else if(this.currentToken.getKind() == GrammarSymbols.CONTINUE){
			acceptIt();
			accept(GrammarSymbols.SEMICOLON);
			statement = new ContinueStatement();
		}
		else{
			accept(GrammarSymbols.PRINTLN);
			accept(GrammarSymbols.LPAR);
			Identifier identifier = new Identifier(this.currentToken.getSpelling());
			accept(GrammarSymbols.ID);
			accept(GrammarSymbols.RPAR);
			accept(GrammarSymbols.SEMICOLON);
			statement = new PrintlnStatement(identifier);
		}
		
		return statement;
	}
	
	private boolean isValue(int kind){
		if(kind == GrammarSymbols.ID ||
			kind == GrammarSymbols.NUMBER ||
			kind == GrammarSymbols.FALSE ||
			kind == GrammarSymbols.TRUE)
		{
			return true;
		}
		else{
			return false;
		}
	}
	
	//Statement*
	private ArrayList<Statement> parseStatements() throws SyntacticException, LexicalException{
		
		ArrayList<Statement> sts = new ArrayList<Statement>();
		
		while(this.currentToken.getKind() == GrammarSymbols.ID ||
			this.currentToken.getKind() == GrammarSymbols.IF ||
			this.currentToken.getKind() == GrammarSymbols.WHILE ||
			this.currentToken.getKind() == GrammarSymbols.RETURN ||
			this.currentToken.getKind() == GrammarSymbols.RETURN ||
			this.currentToken.getKind() == GrammarSymbols.BREAK ||
			this.currentToken.getKind() == GrammarSymbols.CONTINUE ||
			this.currentToken.getKind() == GrammarSymbols.PRINTLN){

			Statement statement = parseStatement();
			sts.add(statement);
		}
		return sts;
	}
	
	//(Type "identifier" ";")* Statements
	private FunctionBody parseFunctionBody() throws SyntacticException, LexicalException{
		
		ArrayList<VariableDeclaration> vds = new ArrayList<VariableDeclaration>();
		
		while(this.currentToken.getKind() == GrammarSymbols.VOID ||
			this.currentToken.getKind() == GrammarSymbols.INT ||
			this.currentToken.getKind() == GrammarSymbols.DOUBLE ||
			this.currentToken.getKind() == GrammarSymbols.BOOLEAN)
		{
			Type type = parseType();
			Identifier identifier = new Identifier(this.currentToken.getSpelling());
			accept(GrammarSymbols.ID);
			accept(GrammarSymbols.SEMICOLON);
			VariableDeclaration vd = new VariableDeclaration(type, identifier);
			vds.add(vd);
		}
		ArrayList<Statement> sts = new ArrayList<Statement>();
		sts = parseStatements();
			
		FunctionBody fb = new FunctionBody(vds, sts);
		return fb;
	}
	
	//Type "identifier" (empty | "," Parameters)
	private ArrayList<VariableDeclaration> parseParameters() throws SyntacticException, LexicalException{
		
		ArrayList<VariableDeclaration> vds = new ArrayList<VariableDeclaration>();
		
		Type type = parseType();
		Identifier identifier = new Identifier(this.currentToken.getSpelling());
		accept(GrammarSymbols.ID);
		VariableDeclaration vd = new VariableDeclaration(type, identifier);
		vds.add(vd);
		
		while(this.currentToken.getKind() == GrammarSymbols.COMMA){
			acceptIt();
			type = parseType();
			identifier = new Identifier(this.currentToken.getSpelling());
			accept(GrammarSymbols.ID);
			vd = new VariableDeclaration(type, identifier);
			vds.add(vd);
		}
		
		return vds;
	}
	
	//"void" | "int" | "double" | "boolean"
	private Type parseType() throws SyntacticException, LexicalException{
		Type type = new Type(this.currentToken.getSpelling());
		switch(this.currentToken.getKind()){
			case GrammarSymbols.VOID:
			case GrammarSymbols.INT:
			case GrammarSymbols.DOUBLE:
				acceptIt();
				break;
			default:
				accept(GrammarSymbols.BOOLEAN);
		}
		
		return type;
	}
	
	//(Type "identifier" ( ";" | "(" (Parameters | empty) ")" "{" FunctionBody "}" ))* eot
	private Program parseProgram() throws SyntacticException, LexicalException{
		
		cmds = new ArrayList<Command>();
		Command command;
		while(this.currentToken.getKind() == GrammarSymbols.VOID ||
				this.currentToken.getKind() == GrammarSymbols.INT ||
				this.currentToken.getKind() == GrammarSymbols.DOUBLE ||
				this.currentToken.getKind() == GrammarSymbols.BOOLEAN)
		{
			Type type = parseType();
			Identifier identifier = new Identifier(this.currentToken.getSpelling());
			accept(GrammarSymbols.ID);	
			if(this.currentToken.getKind() == GrammarSymbols.SEMICOLON){
				acceptIt();
				command = new VariableDeclaration(type, identifier);
			}
			else{
				accept(GrammarSymbols.LPAR);
				ArrayList<VariableDeclaration> vds = new ArrayList<VariableDeclaration>();
				if(this.currentToken.getKind() == GrammarSymbols.VOID ||
						this.currentToken.getKind() == GrammarSymbols.INT ||
						this.currentToken.getKind() == GrammarSymbols.DOUBLE ||
						this.currentToken.getKind() == GrammarSymbols.BOOLEAN)
				{
					vds = parseParameters();
				}
				accept(GrammarSymbols.RPAR);
				accept(GrammarSymbols.LBRACKET);
				FunctionBody fb = parseFunctionBody();
				accept(GrammarSymbols.RBRACKET);
				command = new FunctionDeclaration(type, identifier, vds, fb);
			}
			cmds.add(command);
		}
		accept(GrammarSymbols.EOT);
		
		Program program = new Program(cmds);
		return program;
	}
	
}
